//==================================================================
//  Copyright (C) 2006-2007  Davide Pasca
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//==================================================================
/// @file TextureBase.cpp
///
///
/// @author Davide Pasca
///
//==================================================================

#include "stdafx.h"

#include "Memfile.h"
#include "rend_device.h"
#include "texture_base.h"

//==================================================================
using namespace	PUtils;

//==================================================================
static void convert32_from_24( u_char *desp, const u_char *srcp, int w, int h, int spitch, u_char alpha_val )
{
	for (int i=0; i < h; ++i)
	{
		const u_char	*srcp2 = srcp;
		for (int j=w; j; --j, desp += 4, srcp2 += 3)
		{
#ifdef TARGET_D3D
			desp[0] = alpha_val;
			desp[1] = srcp2[0];
			desp[2] = srcp2[1];
			desp[3] = srcp2[2];

#elif defined(TARGET_OPENGL)
			desp[0] = srcp2[0];
			desp[1] = srcp2[1];
			desp[2] = srcp2[2];
			desp[3] = alpha_val;

#endif
		}
		srcp += spitch;
	}
}

//==================================================================
static int log2( u_int val )
{
	int log2;

	for (log2=0; (1 << log2) < val; ++log2)
		;

	return log2;
}

//==================================================================
TextureBase::TextureBase( ImageBase *image_basep, RendDevice *rdevicep ) :
	_owns_image_base(false),
	_owns_texture_object(true),
	_is_hardware_only(false),
#if defined(TARGET_D3D)
	_d3dtex_cp(NULL),

#elif defined(TARGET_OPENGL)
	_tex_ogl_id(0),
	
#endif
	_image_basep(image_basep),
	_rdevicep(rdevicep)
{
	UpdateTexture();
	onSizeChange();
}

//==================================================================
TextureBase::TextureBase( const TCHAR *filenamep, ImageBase::LoadParams *paramsp, RendDevice *rdevicep ) :
	_owns_image_base(true),
	_owns_texture_object(true),
	_is_hardware_only(false),
#if defined(TARGET_D3D)
	_d3dtex_cp(NULL),

#elif defined(TARGET_OPENGL)
	_tex_ogl_id(0),

#endif
	_image_basep(NULL),
	_rdevicep(rdevicep),
	_w2(0),
	_h2(0)
{
	LoadTexture( filenamep, paramsp );
}

//==================================================================
TextureBase::TextureBase( u_int w, u_int h, RendDevice *rdevicep ) :
	_owns_image_base(false),
	_owns_texture_object(false),
	_is_hardware_only(true),
#if defined(TARGET_D3D)
	_d3dtex_cp(NULL),

#elif defined(TARGET_OPENGL)
	_tex_ogl_id(0),

#endif
	_image_basep(NULL),
	_rdevicep(rdevicep),
	_w2(0),
	_h2(0)
{
	_w2 = log2( w );
	_h2 = log2( h );
}

//==================================================================
void TextureBase::LoadTexture( const TCHAR *filenamep, ImageBase::LoadParams *paramsp )
{
	if ( _owns_image_base )
	{
		SAFE_DELETE( _image_basep );
		_owns_image_base = false;
	}

	ImageBase::LoadParams	defparams;
	if NOT( paramsp )
	{
		defparams._do_convert_truecolor = true;
		defparams._do_force_alpha = true;
		paramsp = &defparams;
	}

	_owns_image_base = true;
	_image_basep = new ImageBase();
	_image_basep->LoadAutodetect( filenamep, paramsp );

	UpdateTexture();
	onSizeChange();
}

//==================================================================
void TextureBase::onSizeChange()
{
	_w2 = log2( _image_basep->GetWidth() );
	_h2 = log2( _image_basep->GetHeight() );
#if defined(TARGET_D3D)
	_scoe = 1.0f / _image_basep->GetWidth();
	_tcoe = 1.0f / _image_basep->GetHeight();

#elif defined(TARGET_OPENGL)
	_scoe = 1.0f / (1 << _w2);
	_tcoe = 1.0f / (1 << _h2);

#endif
}

//==================================================================
TextureBase::~TextureBase()
{
	if ( _owns_image_base )
		SAFE_DELETE( _image_basep );

	if ( _owns_texture_object )
	{
#if defined(TARGET_D3D)
		if ( _d3dtex_cp )
			_d3dtex_cp->Release();

#elif defined(TARGET_OPENGL)
		if ( _tex_ogl_id != 0 )
			glDeleteTextures( 1, &_tex_ogl_id );
#endif
	}
}

//==================================================================
#if 0
void TextureBase::SelectTexture()
{
/*
	if ( _source_has_changed )
	{
		_source_has_changed = false;
		uploadTexture();
	}
*/
#ifdef TARGET_D3D
	PASSERT( _d3dtex_cp != NULL );
	D3DDevice	*d3ddevp = _rdevicep->GetD3DDevice();
	d3ddevp->SetTexture( 0, _d3dtex_cp );

#elif defined(TARGET_OPENGL)
	glEnable( GL_TEXTURE_2D );
	glBindTexture( GL_TEXTURE_2D, _tex_ogl_id );
	
#endif
}
#endif

#if defined(TARGET_D3D)
//==================================================================
static void upload_d3d_tex88( D3DTexture *texp, int w, int h, const u_char *datap, int src_pitch )
{
D3DLOCKED_RECT	lr;
u_char			*desp;

	if ( texp->LockRect( 0, &lr, NULL, 0 ) )
	{
		PASSERT( 0 );
		return;
	}

	PASSERT( src_pitch <= lr.Pitch );

	desp = (u_char *)lr.pBits;


	POINT	dest_off = { 0, 0 };
	RECT	src_blocks_rect = { 0, 0, w, h };
	XGTileSurface( desp, w, h, &dest_off, datap, src_pitch, &src_blocks_rect, 2 );


	texp->UnlockRect( 0 );
	
}

//==================================================================
static void upload_d3d_tex32( D3DTexture *texp, int w, int h, const u_char *datap, int src_pitch,
							  bool source_is_24 )
{
	D3DLOCKED_RECT	lr;
	u_char			*desp;

	u_char	*tmp_datap = NULL;
	int		use_pitch = src_pitch;

	if ( source_is_24 )
	{
		try {
			tmp_datap = new u_char[ w * h * 4 ];
		}catch(...){
			throw;
			return;
		}

		convert32_from_24( tmp_datap, datap, w, h, src_pitch, 255 );
		use_pitch = w * sizeof(u_char) * 4;
	}

	if ( texp->LockRect( 0, &lr, NULL, 0 ) )
	{
		PASSERT( 0 );
		return;
	}

	PASSERT( src_pitch <= lr.Pitch );

	desp = (u_char *)lr.pBits;

	POINT	dest_off = { 0, 0 };
	RECT	src_blocks_rect = { 0, 0, w, h };
	XGTileSurface( desp, w, h, &dest_off, tmp_datap ? tmp_datap : datap, use_pitch, &src_blocks_rect, 4 );

	texp->UnlockRect( 0 );

	SAFE_DELETE( tmp_datap );

}
#elif defined(TARGET_OPENGL)

//==================================================================
static void upload_ogl_tex32( int w, int h, const u_char *datap, int src_pitch, bool source_is_24 )
{
//#define MAKEFAKEDATA
#ifdef MAKEFAKEDATA
	u_char			*tmp_datap = NULL;

	datap = tmp_datap = new u_char[ w * h * 4 ];
	for (int i=0; i < w*h*4; ++i)
	{
		tmp_datap[i] = rand();
	}
#endif

	if ( source_is_24 )
	{
		//convert32_from_24( tmp_datap, datap, w, h, src_pitch, 255 );
		PGLERRORCHECK;
		//glTexSubImage2D( GL_TEXTURE_2D, 0, 0,0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, tmp_datap );
		glTexSubImage2D( GL_TEXTURE_2D, 0, 0,0, w, h, GL_RGB, GL_UNSIGNED_BYTE, datap );
		PGLERRORCHECK;
	}
	else
	{
		PGLERRORCHECK;
		glTexSubImage2D( GL_TEXTURE_2D, 0, 0,0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, datap );
		PGLERRORCHECK;
	}

#ifdef MAKEFAKEDATA
	SAFE_DELETE( tmp_datap );
#endif
}
#endif

//==================================================================
bool TextureBase::uploadTexture()
{
	// hardware only textures that have no imagebase don't need to be uploaded !
	if NOT( _image_basep )
	{
		return true;
	}

	bool	source_is_24 = false;
	int		w = _image_basep->GetWidth();
	int		h = _image_basep->GetHeight();

#ifdef TARGET_D3D
	D3DFORMAT	format;

	switch ( _image_basep->GetFormat().GetAsCommonFormat() )
	{
	case ImageBase::BF_A8L8:
		format = D3DFMT_A8L8;
		break;

	case ImageBase::BF_BGR888:
		source_is_24 = true;
		format = D3DFMT_X8B8G8R8;
		break;

	case ImageBase::BF_ABGR8888:
		PASSERT( _image_basep->HasAlpha() );
		format = D3DFMT_A8B8G8R8;
		break;

	case ImageBase::BF_XBGR8888:
		PASSERT( !_image_basep->HasAlpha() );
		format = D3DFMT_X8B8G8R8;
		break;

	default:
		PASSERT( 0 );	// not really supported for now
		return false;
		break;
	}

	if NOT( _d3dtex_cp )
	{
		D3DDevice	*d3ddevp = _rdevicep->GetD3DDevice();
		if ( d3ddevp->CreateTexture( w, h, 1, 0, format, D3DPOOL_DEFAULT, &_d3dtex_cp, NULL ) )
		{
			PASSERT( 0 );
			throw;
			return false;
		}
	}

	if ( format == D3DFMT_A8L8 )
		upload_d3d_tex88( _d3dtex_cp, w, h, _image_basep->GetData(), _image_basep->GetPitch() );
	else
		upload_d3d_tex32( _d3dtex_cp, w, h, _image_basep->GetData(), _image_basep->GetPitch(), source_is_24 );
	
#elif defined(TARGET_OPENGL)

	if ( _tex_ogl_id == 0 )
	{
		glGenTextures( 1, &_tex_ogl_id );
		PGLERRORCHECK;
	
		glBindTexture( GL_TEXTURE_2D, _tex_ogl_id );
		PGLERRORCHECK;

		int	ww = w;
		int	hh = h;
/*
		if ( w != 1 << log2( w ) )
			ww = 1 << (log2( w ));

		if ( h != 1 << log2( h ) )
			hh = 1 << (log2( h ));
*/
		switch ( _image_basep->GetBaseFormat() )
		{
		case ImageBase::BF_INDEX8:
			glTexImage2D( GL_TEXTURE_2D, 0, 1, ww, hh, 0, GL_ALPHA, GL_UNSIGNED_BYTE, NULL );
			break;

		case ImageBase::BF_L8A8:
			glTexImage2D( GL_TEXTURE_2D, 0, 2, ww, hh, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, NULL );
			break;

		case ImageBase::BF_L8:
			glTexImage2D( GL_TEXTURE_2D, 0, 1, ww, hh, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL );
			break;

		case ImageBase::BF_R8G8B8:
			glTexImage2D( GL_TEXTURE_2D, 0, 3, ww, hh, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL );
			break;

		case ImageBase::BF_B8G8R8:
			glTexImage2D( GL_TEXTURE_2D, 0, 3, ww, hh, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, NULL );
			break;

		case ImageBase::BF_R8G8B8A8:
			glTexImage2D( GL_TEXTURE_2D, 0, 4, ww, hh, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
			break;

		case ImageBase::BF_B8G8R8A8:
			glTexImage2D( GL_TEXTURE_2D, 0, 4, ww, hh, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, NULL );
			break;

		default:
			PASSERT( 0 );	// not really supported for now
			return false;
			break;
		}

		PGLERRORCHECK;
	}

	glBindTexture( GL_TEXTURE_2D, _tex_ogl_id );
	PGLERRORCHECK;

	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	PGLERRORCHECK;

	switch ( _image_basep->GetBaseFormat() )
	{
	case ImageBase::BF_INDEX8:
		glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, w, h, GL_ALPHA, GL_UNSIGNED_BYTE, _image_basep->GetData() );
		PGLERRORCHECK;
		break;

	case ImageBase::BF_L8A8:
		glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, w, h, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, _image_basep->GetData() );
		PGLERRORCHECK;
		break;

	case ImageBase::BF_L8:
		glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, w, h, GL_LUMINANCE, GL_UNSIGNED_BYTE, _image_basep->GetData() );
		PGLERRORCHECK;
		break;

	case ImageBase::BF_R8G8B8:
		upload_ogl_tex32( w, h, _image_basep->GetData(), _image_basep->GetImagePitch(), true );
		break;

	case ImageBase::BF_R8G8B8A8:
		upload_ogl_tex32( w, h, _image_basep->GetData(), _image_basep->GetImagePitch(), false );
		break;

	default:
		PASSERT( 0 );	// not really supported for now
		return false;
		break;
	}

#endif

	return true;
}
